<!--
* Copyright 2012 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
-->

<!doctype html>

<meta charset="utf-8"/>
<title>ParSeq JSON Trace Viewer</title>

<link rel="shortcut icon" href="img/icon.png">
<link href="bootstrap/css/bootstrap.min.css" rel="stylesheet">

<link rel="stylesheet" href="css/table.css">
<link rel="stylesheet" href="css/waterfall.css">
<link rel="stylesheet" href="css/d3.slider.css">

<style>
.page-content {
  padding-top: 1rem;
}

.nav-header {
  display: block;
  margin-bottom: 0.5rem;
}

.nav-pills .nav-link svg {
  fill: var(--blue);
}

.nav-pills .nav-link:hover svg {
  fill: #0056b3;
}

.nav-pills .nav-link.active svg {
  fill: var(--white);
}

.icon {
  width: 20px;
  height: 20px;
  display: inline-block;
}

.view-selector {
  display: flex;
  align-items: center;
  gap: 0.8rem;
}

textarea {
  width: 760px;
}

textarea.error,
textarea.is-invalid {
  color: var(--red);
  border-color: var(--red);
}

.text-monospace {
  font-family: monospace;
}

.label {
  font-weight: bold;
}

#resultView {
  font-size: 0.875rem;
  overflow-x: auto;
}
</style>

<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
  <symbol id="shapes-square" viewBox="0 0 195.74 129.42">
    <rect x="0.5" y="0.5" width="194.74" height="128.42" fill="blue" stroke="#000" stroke-miterlimit="10"/>
  </symbol>

  <symbol id="svg-icon-timetable" viewBox="0 0 24 24">
    <path d="M14,12H15.5V14.82L17.94,16.23L17.19,17.53L14,15.69V12M4,2H18A2,2 0 0,1 20,4V10.1C21.24,11.36 22,13.09 22,15A7,7 0 0,1 15,22C13.09,22 11.36,21.24 10.1,20H4A2,2 0 0,1 2,18V4A2,2 0 0,1 4,2M4,15V18H8.67C8.24,17.09 8,16.07 8,15H4M4,8H10V5H4V8M18,8V5H12V8H18M4,13H8.29C8.63,11.85 9.26,10.82 10.1,10H4V13M15,10.15A4.85,4.85 0 0,0 10.15,15C10.15,17.68 12.32,19.85 15,19.85A4.85,4.85 0 0,0 19.85,15C19.85,12.32 17.68,10.15 15,10.15Z" />
  </symbol>

  <symbol id="svg-icon-timeline" viewBox="0 0 24 24">
    <path d="M2,2H4V20H22V22H2V2M7,10H17V13H7V10M11,15H21V18H11V15M6,4H22V8H20V6H8V8H6V4Z" />
  </symbol>

  <symbol id="svg-icon-graph-outline" viewBox="0 0 24 24">
    <path d="M19.5 17C19.36 17 19.24 17 19.11 17.04L17.5 13.8C17.95 13.35 18.25 12.71 18.25 12C18.25 10.62 17.13 9.5 15.75 9.5C15.61 9.5 15.5 9.5 15.35 9.54L13.74 6.3C14.21 5.84 14.5 5.21 14.5 4.5C14.5 3.12 13.38 2 12 2S9.5 3.12 9.5 4.5C9.5 5.2 9.79 5.84 10.26 6.29L8.65 9.54C8.5 9.5 8.39 9.5 8.25 9.5C6.87 9.5 5.75 10.62 5.75 12C5.75 12.71 6.04 13.34 6.5 13.79L4.89 17.04C4.76 17 4.64 17 4.5 17C3.12 17 2 18.12 2 19.5C2 20.88 3.12 22 4.5 22S7 20.88 7 19.5C7 18.8 6.71 18.16 6.24 17.71L7.86 14.46C8 14.5 8.12 14.5 8.25 14.5C8.38 14.5 8.5 14.5 8.63 14.46L10.26 17.71C9.79 18.16 9.5 18.8 9.5 19.5C9.5 20.88 10.62 22 12 22S14.5 20.88 14.5 19.5C14.5 18.12 13.38 17 12 17C11.87 17 11.74 17 11.61 17.04L10 13.8C10.45 13.35 10.75 12.71 10.75 12C10.75 11.3 10.46 10.67 10 10.21L11.61 6.96C11.74 7 11.87 7 12 7C12.13 7 12.26 7 12.39 6.96L14 10.21C13.54 10.66 13.25 11.3 13.25 12C13.25 13.38 14.37 14.5 15.75 14.5C15.88 14.5 16 14.5 16.13 14.46L17.76 17.71C17.29 18.16 17 18.8 17 19.5C17 20.88 18.12 22 19.5 22S22 20.88 22 19.5C22 18.12 20.88 17 19.5 17M4.5 20.5C3.95 20.5 3.5 20.05 3.5 19.5S3.95 18.5 4.5 18.5 5.5 18.95 5.5 19.5 5.05 20.5 4.5 20.5M13 19.5C13 20.05 12.55 20.5 12 20.5S11 20.05 11 19.5 11.45 18.5 12 18.5 13 18.95 13 19.5M7.25 12C7.25 11.45 7.7 11 8.25 11S9.25 11.45 9.25 12 8.8 13 8.25 13 7.25 12.55 7.25 12M11 4.5C11 3.95 11.45 3.5 12 3.5S13 3.95 13 4.5 12.55 5.5 12 5.5 11 5.05 11 4.5M14.75 12C14.75 11.45 15.2 11 15.75 11S16.75 11.45 16.75 12 16.3 13 15.75 13 14.75 12.55 14.75 12M19.5 20.5C18.95 20.5 18.5 20.05 18.5 19.5S18.95 18.5 19.5 18.5 20.5 18.95 20.5 19.5 20.05 20.5 19.5 20.5Z" />
  </symbol>
</svg>

<div class="navbar navbar-dark bg-info">
  <h1 id="brand" class="navbar-brand mb-0">ParSeq JSON Trace Viewer</h1>
  <span class="navbar-text">vX.X.X</span>
</div>

<div class="container-fluid page-content bg-light border-bottom pb-3" id="app-container">
  <div class="row-fluid">
    <form action="javascript:void(0);">
      <fieldset class="row">
        <div class="col-2">
          <ul class="nav nav-pills flex-column">
            <li class="nav-item"><strong class="nav-header">Views</strong></li>
            <li class="nav-item">
              <a class="nav-link active view-selector" href="#" id="viewWaterfall" data-view="waterfall">
                <svg class="icon icon-inline">
                  <use href="#svg-icon-timeline" />
                </svg>
                Waterfall
              </a>
            </li>
            <li class="nav-item">
              <a class="nav-link view-selector" href="#" id="viewGraphviz" data-view="graphviz">
                <svg class="icon icon-inline">
                  <use href="#svg-icon-graph-outline" />
                </svg>
                Graphviz
              </a>
            </li>
            <li class="nav-item">
              <a class="nav-link view-selector" href="#" id="viewTable" data-view="table">
                <svg class="icon icon-inline">
                  <use href="#svg-icon-timetable" />
                </svg>
                Table
              </a>
            </li>
          </ul>
        </div>
        <div class="col-10">
          <div class="form-group form-row">
            <label for="jsonText"><strong>JSON Trace Data</strong></label>
            <textarea
                    id="jsonText"
                    rows="3"
                    placeholder="Insert JSON trace here"
                    spellcheck="false"
                    class="form-control text-monospace"></textarea>
            <div id="jsonTextFeedback" class="invalid-feedback">
              Invalid JSON trace data.
            </div>
          </div>
          <div class="form-row" id="taskFilters">
            <div class="form-check form-check-inline">
              <input class="form-check-input" id="checkboxExcludeUser" type="checkbox" />
              <label class="form-check-label" for="checkboxExcludeUser"><em>User</em> hidden tasks</label>
            </div>
            <div class="form-check form-check-inline">
              <input class="form-check-input" id="checkboxExcludeSystem" type="checkbox" />
              <label class="form-check-label" for="checkboxExcludeSystem"><em>System</em> hidden tasks</label>
            </div>
            <div class="form-check form-check-inline">
              <input class="form-check-input" id="checkboxExcludeParent" type="checkbox" checked="checked" />
              <label class="form-check-label" for="checkboxExcludeParent">Parent tasks</label>
            </div>
            <div class="form-check form-check-inline">
              <input class="form-check-input" id="checkboxExcludeNonCritical" type="checkbox" />
              <label class="form-check-label" for="checkboxExcludeNonCritical">Critical path</label>
            </div>
          </div>
        </div>
      </fieldset>
    </form>
  </div>
</div>

<div id="resultView" class="container-fluid mt-3 mb-3"></div>

<script src="js/d3.v3.min.js"></script>
<script src="build/parseq-tracevis.js"></script>
<script src="node_modules/svg-injector/svg-injector.js"></script>
<script src="node_modules/svg-pan-zoom/dist/svg-pan-zoom.js"></script>
<script src="js/d3.slider.js"></script>

<script>
  var
    WATERFALL = parseqTracevis.renderWaterfall,
    GRAPHVIZ = parseqTracevis.renderGraphviz,
    TABLE = parseqTracevis.renderTable;

var inputJSON = d3.select("#jsonText");
var inputJSONFeedback = document.getElementById('jsonTextFeedback');
var taskFilters = document.getElementById('taskFilters').querySelectorAll('input[type="checkbox"]');

var currentView;
setView("waterfall");

function setView(view) {
  var prevView = selectView(currentView);
  if (prevView) {
    prevView.classed("active", false);
  }
  currentView = view;
  selectView(view).classed("active", true);
}

function selectView(view) {
  switch (view) {
    case "waterfall": return d3.select("#viewWaterfall");
    case "graphviz": return d3.select("#viewGraphviz");
    case "table": return d3.select("#viewTable");
  }
}

function refreshView() {
  var view;
  switch (currentView) {
    case "waterfall": view = WATERFALL; break;
    case "graphviz": view = GRAPHVIZ; break;
    case "table": view = TABLE; break;
  }

  d3.selectAll("#resultView *").remove();
  var jsonText = inputJSON.property("value");

  if (!jsonText.length || !jsonText.trim().length) {
    inputJSON.classed("is-invalid", false);
    inputJSON.attr("title", null);
    return;
  }

  var json, g;
  try {
    json = JSON.parse(jsonText);
    g = parseqTracevis.trace.parse(json);
    // Suppress any error state related to the trace JSON.
    inputJSON.classed("is-invalid", false);
    inputJSON.attr("title", null);
  } catch (err) {
    // Display an error state related to the trace JSON.
    inputJSON.classed("is-invalid", true);
    const errorFeedback = "Parsing trace failed: " + err;
    inputJSON.attr("title", errorFeedback);
    inputJSONFeedback.textContent = errorFeedback;
    throw err;
  }

  if (!d3.select("#checkboxExcludeUser").property("checked")) {
    parseqTracevis.trace.excludeUserTasks(g);
  }
  if (!d3.select("#checkboxExcludeSystem").property("checked")) {
    parseqTracevis.trace.excludeSystemTasks(g);
  }
  if (!d3.select("#checkboxExcludeParent").property("checked")) {
    parseqTracevis.trace.excludeParentTasks(g);
  }
  if (d3.select("#checkboxExcludeNonCritical").property("checked")) {
    parseqTracevis.trace.excludeNonCriticalTasks(parseqTracevis.trace, g);
  }
  view(d3.select("#resultView").attr('class', 'container-fluid mt-3 mb-3'), g);
}

inputJSON.node().addEventListener('input', refreshView, false);

/**
 * This is a utility that abstracts the details of how to programmatically load
 * a trace and render it. If a view is not supplied then the current view is
 * used. The default view is "waterfall".
 *
 * This function should be preferred to direct use of DOM ids / functions as
 * they may change in the future.
 */
function renderTrace(trace, view) {
  inputJSON.property("value", trace);
  if (arguments.length > 1) setView(view);
  refreshView();
}

// Set up view changing buttons.
['viewWaterfall', 'viewGraphviz', 'viewTable'].forEach(id => {
  const element = document.getElementById(id);

  if (element) {
    const view = element.dataset.view;

    if (view) {
      element.addEventListener('click', event => {
        event.preventDefault();
        event.stopPropagation()

        setView(view);
        refreshView();
      });
    }
  }
});

// Set up view refreshing for task filters.
taskFilters.forEach(element => {
  element.addEventListener('click', () => refreshView());
});

// If the URL contains a trace, inject it into the input textarea
var urlSearchParams = new URLSearchParams(window.location.search);

if (urlSearchParams.has('trace')) {
  renderTrace(urlSearchParams.get('trace'));
}
</script>
